/*******************************************************************************
 * Copyright (c) 2016, 2025 Johannes Messmer (admin@jomess.com), fortiss GmbH
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Johannes Messmer - initial API and implementation and/or initial documentation
 *   Jose Cabral - Cleaning of namespaces
 *******************************************************************************/

#include "spi.h"
#include <sstream>
#include <string>
#include "forte/util/devlog.h"
#include <sys/ioctl.h>

extern "C" { // missing in some versions of spidev.h
#include <linux/spi/spidev.h>
}

namespace forte::eclipse4diac::io::embrick {

  unsigned long const EmbrickSPIHandler::scmDefaultSpiSpeed = 300000;
  unsigned long const EmbrickSPIHandler::scmMaxSpiSpeed = 700000;

  namespace {
    char const mSpiMode = SPI_CPHA;
    char const mSpiBitOrder = 0; // MSB first

    const char *const scmFailedToInitHandler = "Failed to init spidev handler. Check if spi is enabled.";
    const char *const scmFailedToConfigMode = "Failed to config spi write mode.";
    const char *const scmFailedToConfigBitOrder = "Failed to config spi bit order.";
    const char *const scmFailedToConfigSpeed = "Failed to config spi speed.";
    const char *const scmFailedToTestBus = "Failed to send test byte to spi.";
    const char *const scmFailedToTransferBuffer = "Failed to transfer buffer via spi.";

  } // namespace

  EmbrickSPIHandler::EmbrickSPIHandler(unsigned int paInterface) : mError(nullptr) {
    // Convert int to string
    std::ostringstream interfaceStream;
    interfaceStream << paInterface;
    std::string interfaceStr = interfaceStream.str();

    std::string spidev = "/dev/spidev" + interfaceStr + ".0";
    init(spidev.c_str());
  }

  EmbrickSPIHandler::~EmbrickSPIHandler() {
    deInit();
  }

  void EmbrickSPIHandler::init(const char *paSpidev) {
    // Init spidev
    DEVLOG_DEBUG("emBrick[SPIHandler]: Open spidev '%s'\n", paSpidev);
    mFd = open(paSpidev, O_RDWR);

    if (mFd < 0) {
      return fail(scmFailedToInitHandler);
    }
    // Set mode to SPI_CPOL and SPI_CPHA
    if (!config(SPI_IOC_WR_MODE, SPI_IOC_RD_MODE, mSpiMode)) {
      return fail(scmFailedToConfigMode);
    }
    // Set MSB (Most Significant Bit First)
    if (!config(SPI_IOC_WR_LSB_FIRST, SPI_IOC_RD_LSB_FIRST, mSpiBitOrder)) {
      return fail(scmFailedToConfigBitOrder);
    }
    // Set speed
    if (!config(SPI_IOC_WR_MAX_SPEED_HZ, SPI_IOC_RD_MAX_SPEED_HZ, scmMaxSpiSpeed)) {
      return fail(scmFailedToConfigSpeed);
    }
    mSpiSpeed = scmDefaultSpiSpeed;

    // Test
    unsigned char testByte[1] = {0};
    if (!transfer(testByte, testByte, 1)) {
      return fail(scmFailedToTestBus);
    }
    DEVLOG_INFO("emBrick[SPIHandler]: Ready.\n");
  }

  void EmbrickSPIHandler::deInit() {
    // Close handler if open
    if (mFd >= 0) {
      close(mFd);
    }
  }

  template<typename T>
  bool EmbrickSPIHandler::config(unsigned int paConfig, unsigned int paConfigVerify, T paValue) {
    int status;

    // Set config via ioctl
    status = ioctl(mFd, paConfig, &paValue);
    if (status < 0) {
      return false;
    }
    // Verify config
    T valueCheck;
    status = ioctl(mFd, paConfigVerify, &valueCheck);
    if (status < 0) {
      return false;
    }
    return valueCheck == paValue;
  }

  void EmbrickSPIHandler::fail(const char *paReason) {
    mError = paReason;
    DEVLOG_ERROR("emBrick[SPIHandler]: %s\n", mError);
  }

  bool EmbrickSPIHandler::transfer(unsigned char *paSendBuffer, unsigned char *paReceiveBuffer, int paLength) {

    struct spi_ioc_transfer msg{};

    msg.tx_buf = (unsigned long) paSendBuffer;
    msg.rx_buf = (unsigned long) paReceiveBuffer;
    msg.len = paLength;
    msg.delay_usecs = 0;
    msg.speed_hz = mSpiSpeed;
    msg.bits_per_word = 8;
    msg.cs_change = 0;
    msg.pad = 0;

    // Send data to spi bus
    int status = ioctl(mFd, SPI_IOC_MESSAGE(1), &msg);
    if (status < 0) {
      fail(scmFailedToTransferBuffer);
      return false;
    }

    return true;
  }

  void EmbrickSPIHandler::setSpeed(const unsigned long paSpeed) {
    mSpiSpeed = paSpeed;
  }

} // namespace forte::eclipse4diac::io::embrick
